#!/usr/bin/env python
# -*- coding: ascii -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# dietcherrypy - mini CherryPy replacement
# Copyright (C) 2007  Chris Clark
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Horrible mini cherrypy/rhubarbtart implementation (suitable for older
Python/Jython versions). Missing pretty much all the features except
built in http server and minimal parameter/form handling (and simplistic
cgi support without wsgi).

Seriously, use CherryPy (or RhubarbTart) instead of this unless you need it.

Advantage; very small, should run with older release of Python/Jython.

The word diet in relation to food can mean one of two things:

    1)  low calorie (or fat) replacement for a food that is good
        for you and perfectly replaces the non-diet version.
    2)  low calorie (or fat) replacement for a food that has no
        taste or flavour and is a poor facsimile in comparison to
        the non-diet version.

dietcherry is the #2 type of replacement :-S 

    try:
        import cherrypy
    except ImportError, info:
        import dietcherrypy as cherrypy

It is intended for the simplest of HelloWorld replacements so that the
same form of params=POST/GET values return=web-page apps can be used
with no support for:

    Sessions
    filters
    threading
    url mapping
    static content
    config

I.e. anything that makes CherryPy useful for "proper" web apps.
    
Originally it only had GET support, but both GET and POST support has
been borrowed from http://colorstudy.com/docs/shootout.html for the
http server.

To see this working in cgi mode, simple create a directory called 'cgi-bin'
and place dietcherrypy.py in it. In the parent directory create a python
script that contains the following:

    ## based on information from 
    ## http://mail.python.org/pipermail/tutor/2003-February/020828.html
    ## and comments in stdlib CGIHTTPServer
    ## Open http://localhost:8000/
    ## Open http://localhost:8000/cgi-bin/dietcherrypy.py
    import CGIHTTPServer

    print 'Issue CTRL-C to quit (Windows CPython use CTRL-Break instead)'
    CGIHTTPServer.test()

Has (pure) CGI support (useful under Python 2.3) e.g. for
http://sourceforge.net. See my CherryCGI/wsgi hack
from a few years back in http://yacddb.sf.net.
Pure CGI as python 2.3 does not have wsgi (unless you 
get external files), I'd prefer a single file lib
this is not supposed to be a full CherryPy replacement!
    
Take a look at:
*   CherryCGI http://yacddb.cvs.sourceforge.net/yacddb/yacddb/python/python/yacddb/ui/html/cgi_example/cherrypy_cgi.py?revision=1.1&view=markup
*   http://hg.lukeplant.me.uk/python/luke/file/f5636ec58c0f/lukeplant_me_uk/bibleverses/web/cgi-bin/lib/bibleverses/webutils.py
*   http://dev.nullcube.com/gitweb/?p=countershape;a=summary
*   web2py http://mdp.cti.depaul.edu/

TODO:
*   Clean up CGI GET/POST support.
*   ADD wsgi support, i.e. dietcherrypy could return a wsgi app,
    a little like rhubarbtart. So that deployment under
    wsgi servers/gateways is easy.
*   Add favicon.ico (/favicon.ico) support.
    
"""
import os
import sys
import inspect
import traceback
try:
    import cgitb
except ImportError:
    cgitb = None

# poor but simple test
JYTHON_RUNTIME_DETECTED = hasattr(sys, 'JYTHON_JAR') or str(copyright).find('Jython') > 0

# NOTE json is only need for demo code
try:
    # Python 2.6+
    import json
except ImportError:
    try:
        # from http://code.google.com/p/simplejson
        import simplejson as json
    except ImportError:
        json = None


try:
    UnicodeDecodeError
    UnicodeEncodeError
except NameError:
    # probably Python 2.2 or earlier
    UnicodeDecodeError = UnicodeError
    UnicodeEncodeError = UnicodeError


def format_exc(limit=None):
    """Like print_exc() but return a string."""
    try:
        etype, value, tb = sys.exc_info()
        return ''.join(traceback.format_exception(etype, value, tb, limit))
    finally:
        etype = value = tb = None

if not hasattr(traceback, 'format_exc'):
    # Probably Jython 2.2
    traceback.format_exc = format_exc

orig_stdout = sys.stdout

# Check if we are CGI, NOTE "try: except:" may be faster than .has_key()
running_under_cgi = os.environ.has_key('SCRIPT_NAME')
if running_under_cgi:
    cgitb.enable()
    try:
        import cStringIO as StringIO
    except ImportError:
        import StringIO
    
    # do not allow applications to write to stdout or stderr
    # many webservers will hide errors if CGI handshake goes wrong
    fake_file = StringIO.StringIO()
    sys.stdout = fake_file
    sys.stderr = fake_file
    
    class BaseHTTPRequestHandler(object):
        def __init__(self, cgi_environ):
            #print >>sys.stderr, cgi_environ
            self.__cgi_environ = cgi_environ
            self.__headers={}
            try:
                self.path = self.__cgi_environ['PATH_INFO']
            except KeyError, info:
                self.path = '/' # default to /index
            try:
                self.query_string = self.__cgi_environ['QUERY_STRING']
            except KeyError, info:
                self.query_string = '' # TODO! parameters not yet handled!
            self.client_address = (self.__cgi_environ.get('REMOTE_ADDR', '127.0.0.1'), 0)
            self.headers = {}
            """
            try:
                self.headers['Content-Length'] = self.__cgi_environ['CONTENT_LENGTH'] ## TODO really need case-less dict
            except KeyError, info:
                pass
            """
            self.wfile = orig_stdout
            self.rfile = sys.stdin ## use StringIO?
        def send_response(self, code):
            # deferred until end_headers() is called
            # NOTE under Python basic cgi runner (CGIHTTPServer), Status not handled
            # Apache cgi handles it correctly though
            self._code = code
        def send_header(self, header_name, header_value):
            self.__headers[header_name] = header_value
        def end_headers(self):
            result_str=[]
            result_str.append('%s: %s' % ('Status', self._code))
            for header_name in self.__headers:
                result_str.append('%s: %s' % (header_name, self.__headers[header_name]))
            result_str = '\n'.join(result_str)
            #sys.stderr.write(result_str)
            ## Could check for absence of 'Content-Type', but this isn't really needed as dietcherry always uses it
            orig_stdout.write(result_str)
            orig_stdout.write('\n\n')
            self._code = 200 # reset to default
        def send_error(self, code, message=None):
            ## TODO Implement!! check BaseHTTPServer
            orig_stdout.write('Status: %d' % code)
            if message:
                orig_stdout.write(message) ## cgi escape
            else:
                orig_stdout.write('\n\n')
    class HTTPServer(object):
        def __init__(self, server_address, HTTPHandler):
            self.__cgi_environ = os.environ
            self.__HTTPHandler = HTTPHandler(os.environ)
        def handle_request(self):
            REQUEST_METHOD = self.__cgi_environ['REQUEST_METHOD'] ## GET or POST -- this is also stored in self.__HTTPHandler (but mangled name)
            """
            ## temporarily abandoned this idea as the do_GET method makes assumptions about the path containing '?' (for http server rather than cgi)
            http_request_method = 'do_' + REQUEST_METHOD
            method = getattr(self.__HTTPHandler, http_request_method)
            method()
            ## below is nasty as we change self.__HTTPHandler.query_string
            """
            if REQUEST_METHOD == 'POST':
                self.__HTTPHandler.query_string = self.__HTTPHandler.rfile.read(int(self.__cgi_environ['CONTENT_LENGTH']))
            self.__HTTPHandler.respond()
            dietcherry_stop()
    DIETCHERRY_ENVIRONMENT='cgi'
    ## TODO postpone this print until end
    #print 'Content-Type: text/html'     # HTML is following
    #print ''                            # blank line, end of headers
    
else:
    import BaseHTTPServer
    BaseHTTPRequestHandler=BaseHTTPServer.BaseHTTPRequestHandler
    HTTPServer = BaseHTTPServer.HTTPServer
    DIETCHERRY_ENVIRONMENT='basehttpserver'


import SocketServer
import types
import cgi
import mimetypes

try:
    #raise ImportError
    import cStringIO as StringIO
except ImportError:
    import StringIO

## Used by DietCherryServer
import threading
import time

## "Globals" exported by cherrypy that are "emulated"
__version__ = '0.0.1'
root=None
config = {} ## only honors 'server.socketPort' and/or 'server.socket_port'. The rest are ignored.

# Could use staticmethod() but want to support Python (Jython) 2.1
"""
class server:
    def start():
        dietcherry_start()
    staticmethod(start)
"""
#class DietCherryServer(object):
class DietCherryServer:
    """CherryPy emulation web server"""
    def _real_start(self):
        ### take advantage of dict.update behavior....
        temp_portnum = None
        temp_config = config.get('updateMap')
        if temp_config:
            temp_portnum = temp_config.get('server.socketPort')
        dietcherry_start(server_port=temp_portnum)
    def start(self):
        if engine.callback_func:
            self.start_with_callback(self, engine.callback_func)
        else:
            self._real_start()
    def stop(self):
        dietcherry_stop()
    def wait(self):
        """wait until web server is started/ready"""
        while not server_is_ready:
            time.sleep(.1)
    def start_with_callback(self, func, args=None, kwargs=None):
        """Start server then call the callback 'func' in a new thread."""
        if args is None:
            args = ()
        if kwargs is None:
            kwargs = {}
        args = (func,) + args
        
        def _callback(func, *args, **kwargs):
            self.wait()
            func(*args, **kwargs)
        threading.Thread(target=_callback, args=args, kwargs=kwargs).start()
        self._real_start()

server = DietCherryServer()

class DietCherryRemoteInfo:
    """Cherrypy cherrypy.request.remote emulation.
    Only works in a single thread as the response is shared as a global"""
    def __init__(self):
        self.ip = None ## CherryPy version ?.x.x

## Cherrypy reponse (and request) headers emulation
## only works in a single thread as the response is shared as a global
#class DietCherryResponse(object):
class DietCherryResponse:
    """Cherrypy reponse headers emulation.
    Only works in a single thread as the response is shared as a global"""
    def __init__(self):
        self.headers={} ## CherryPy version 3.x.x
        self.headerMap=self.headers ## CherryPy version 2.x.x
        ## could try and be clever and only allow access to 'Content-Type' (i.e. error)
        ## FIXME case difference between Content-Type and Content-type which I thih cherrypy was less tight about
        ## also could validate headers as they are set rather than using a plain dict

class DietCherryRequest(DietCherryResponse):
    """Cherrypy reponse headers emulation.
    Only works in a single thread as the response is shared as a global"""
    def __init__(self):
        DietCherryResponse.__init__(self)
        self.remote = DietCherryRemoteInfo()
        self.path_info = None
response = DietCherryResponse()
request = DietCherryRequest()


#class DietCherryEngine(object):
class DietCherryEngine:
    """fake engine, for callback support
    """
    def __init__(self):
        self.callback_func=None
    def subscribe(self, p1, callback_func):
        if p1 != 'start':
            raise NotImplemented('event type %r' % p1)
        self.callback_func=callback_func
    def start_with_callback(self, func, args=None, kwargs=None):
        # crazy back call to server
        server.start_with_callback(func, args, kwargs)
engine=DietCherryEngine()

def quickstart(root, script_name='', config=None):
    """cherrypy v3 quick start
    script_name is ignored
    config is ignored
    """
    dietcherry_start(root_class=root)


# cherrypy.lib.static
#def serve_file(path, content_type=None, disposition=None, name=None):
def serve_file(path, content_type=None):
    """partial clone of cherrypy version"""
    if content_type is None:
        content_type = mimetypes.guess_type(path)
    response.headers['Content-Type'] = content_type
    try:
        result = open(path, 'rb').read()
    except IOError:
        result = 'file not found' ## TODO 404
        raise HTTPError(404)
    return result


class DietCherryPyBaseException(Exception):
    '''Base DietCherryPyBaseException exception'''

class HTTPError(DietCherryPyBaseException):
    """Http error exception, emulates HTTPError"""
    def __init__(self, httpcode):
        self.httpcode = httpcode
        
    def __repr__(self):
        return repr(self.__dict__)
    
    def __str__(self):
        return repr(self.__dict__)
    
    def error_tuple(self):
        error_code = self.httpcode
        error_text, longmessage = BaseHTTPServer.BaseHTTPRequestHandler.responses[error_code]
        return (error_code, error_text, {})

class NotFound(HTTPError):
    def __init__(self):
        HTTPError.__init__(self, 404)

class HTTPRedirect(HTTPError):
    def __init__(self, urls, status=None):
        status = status or 303
        HTTPError.__init__(self, status)
        self.urls = [urls]  # non-standard; assume single param not a list...

    def error_tuple(self):
        error_code = self.httpcode
        error_text = 'Redirect to <a href="%s">%s' % (self.urls[0], self.urls[0])  #  cgi escape..
        headers = {'Location': self.urls[0]}
        # TODO Location for headers...
        return (error_code, error_text, headers)

############## End of CherryPy external api emulation

############## Start of CherryPy internal api emulation

keep_serving=True
server_is_ready=False
dietcherry_root=None
dietcherry_fullmainurl=''

# Show errors to web client? environment == production? NOTE when cgitb is used, this has no impact
SERVER_ERRORS = True
#SERVER_ERRORS = False
DIETCHERRYMODE = os.environ.get('DIETCHERRYMODE')
if DIETCHERRYMODE and DIETCHERRYMODE.startswith('prod'):
    SERVER_ERRORS = False

if JYTHON_RUNTIME_DETECTED and sys.version_info < (2, 3):
    # Jython 2.2
    SERVER_ERRORS_TYPE = 'basic'
else:
    SERVER_ERRORS_TYPE = 'html'

## raise and show exception to stdout?
# WARNING if using internal server, client "hangs" if set to True
SERVER_ERRORS_FATAL = False
#SERVER_ERRORS_FATAL = True

# Non-standard (compared with CherryPy)
PERFORM_UTF8_DECODING_ON_URL_PARAMETERS = True

def expose(func=None):
    def expose_(func):
        func.exposed = True
        return func
    
    if isinstance(func, (types.FunctionType, types.MethodType)):
        # expose is being called directly, before the method has been bound
        return expose_(func)
    else:
        # expose is being called as a decorator
        return expose_

def translate_url_names(func_name):
    """Maps dot/period '.' into underscore (and hypen/dash)"""
    return func_name.replace('.', '_').replace('-', '_')
    
## TODO! function_lookup() should take in root object and not rely on global dietcherry_root
def function_lookup(func_name):
    #import pdb ; pdb.set_trace()
    #DEBUGPRINT print 'DEBUG', 'dietcherry_root', dietcherry_root, func_name
    # renmove trailing slashes in url
    parent_obj = dietcherry_root
    if func_name == '':
        func_name_split = ['index']
    else:
        func_name_split = func_name.split('/', 1)
    result_function = getattr(parent_obj, func_name_split[0], None)
    if getattr(result_function, 'exposed', False):
        func_name_split.pop(0)
        if func_name_split:
            # function that accepts *args (e.g. default function or named with *args)
            #return parent_obj, result_function
            return result_function, result_function
        else:
            return result_function
    
    while func_name.endswith('/'):
        func_name=func_name[:-1]
    
    ## translate invalid python function names like 'hello.html' into 'hello_html', etc.
    func_name = translate_url_names(func_name)
    
    # find subapp/object code
    pathlist = func_name.split('/')
    func_name = pathlist[-1]
    
    pathlist = pathlist[:-1]
    pathlist.reverse()
    #print pathlist
    while pathlist and parent_obj is not None:
        try:
            tempfunc_name = pathlist.pop()
        except AttributeError:
            return None
        parent_obj = getattr(parent_obj, tempfunc_name, None)
    
    # finally lookup function/callable
    result_function = getattr(parent_obj, func_name, None)
    if getattr(result_function, 'exposed', False):
        return result_function
    
    ## check for default handler
    func_name = 'default'
    result_function = getattr(parent_obj, func_name, None)
    if getattr(result_function, 'exposed', False):
        return parent_obj, result_function
    return None


def xmlcharref_encode(encoding, instr):
    """Emulate Python 2.3's 'xmlcharrefreplace' encoding error handler. Assume instr is Uniode"""
    result=[]
    for char in instr:
        try:
            encodedstr = char.encode(encoding, 'strict')
        except UnicodeError:
            encodedstr = '&#%i;' % ord(char)
        result.append(encodedstr)
    return ''.join(result)


FAVICON_STR = '''AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAACDXzIAiF40AI9mMQCQZzMAkmk1AJdoNwCUazcAlm06AKBxNQ
CZcD0Ao3Q5AKZ3PQCoeT8AqntBAKt8QgCsfUQArn5FAK9/RgC3gkEAsYFIALKCSQC
7hUQAtIRMALyGRgC1hU0AvYdHALaGTgC+iUgAuIhQAMCLSgDBjEwAwo1NAMSPTwDG
kVEAFrTkADDA6gAcwfMAJ8LtACzF8AA+xukAFMb6ABzL/wAezP8ARtDuACHQ/gAk0
v8AMdL/ADvU+gAx1fwAPdb9ADTX/wA12P8AR9f9AEDb+wBB2f8AQtv/AEvd/QBN3/
4Aa+H1AFDi/wBY5PwAbeP4AFnl/QBi5v0AY+f/AGvo/wBs6f8Abu39APz//gAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAARUVFRUVFRUVFRUVFRUVFRUVFRUVFMTIsKihFRUVFRUVFRUV
FODQzLi1EKkVFRUVFRUVFRTw5NzYtLipFRUVFRUVFRUVAPjgnIyYiJSJFRUVFCghF
QkA8ODc2MioqKkVFDgwJRT1DPzs4NTItLCokRRQQCwNFOkFAPDg1Ni4qKUUYEQ4LB
EVFRUVFRTQuLixFHhsPDQsJBAUEAEUrNTAtRSAcGhQNCQgFBAQBRTg2MUUhHxkTEQ
sJCQQEBEU7OC9FRUUdFhAJBwIHBgVFRUVFRUVFRUUVEg0LBQUERUVFRUVFRUVFHEQ
QDAoJBEVFRUVFRUVFRR8XFhALCAVFRUVFRf//AAD4PwAA8B8AAPAfAADwBwAAkAMA
ABABAAAIAQAAB+EAAAAhAAAAEQAAABEAAMAfAADwHwAA8B8AAPAfAAA='''


FAVICON = None

def setup_favicon(bitmap_bytes=None):
    if bitmap_bytes is None:
        try:
            import base64
            # Default favicon.ico
            global FAVICON_STR
            bitmap_bytes = base64.decodestring(FAVICON_STR)
            FAVICON_STR = None
        except (ImportError, AttributeError):
            # Maybe Jython?
            # NOTE Jython 2.2 with JVM >1.4 needs an updated socket.py, e.g.
            # http://fisheye3.atlassian.com/browse/~raw,r=3242/jython/trunk/sandbox/kennedya/asynch_sockets/socket.py
            # http://jython.svn.sourceforge.net/viewvc/jython?view=rev&revision=3242
            pass
    global FAVICON
    FAVICON = bitmap_bytes


class MyHTTPServer(HTTPServer):
    """Avoid default Python socket server oddities.
    
     1) Do not perform Fully Qualified Domain Lookup.
        On networks with missing (or poor) DNS, getfqdn() can take over
        5 secs EACH network IO.
     2) Do not allow address re-use.
        On machines where something is already listening on the requested
        port the default Windows socket setting for Python SocketServers
        is to allow the bind to succeed (even though it can't then service
        any requests).
        One possible workaround for Windows is to set the
        DisableAddressSharing registry entry:
        (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Afd\Parameters)
        and reboot. This registry setting prevents multiple sockets from binding
        to the same port and is essentially enabling SO_EXCLUSIVEADDRUSE on
        all sockets. See Java bug 6421091.
    """
    
    allow_reuse_address = False  # Use SO_EXCLUSIVEADDRUSE
    
    def server_bind(self):
        """Override server_bind to store the server name."""
        SocketServer.TCPServer.server_bind(self)
        host, port = self.socket.getsockname()[:2]
        self.server_name = host  # socket.getfqdn(host)
        self.server_port = port


class MyBaseHTTPRequestHandler(BaseHTTPRequestHandler):
    """Do not perform Fully Qualified Domain Lookup.
    One networks with missing (or poor) DNS, getfqdn can take over 5 secs
    EACH network IO"""

    def address_string(self):
        """Return the client address formatted for logging.

        This version looks up the full hostname using gethostbyaddr(),
        and tries to find a name that contains at least one dot.

        """
    
        host, port = self.client_address[:2]
        return host  # socket.getfqdn(host)


class HTTPHandler(MyBaseHTTPRequestHandler):
    """do_GET or do_POST are the entry points -- they are called
    by BaseHTTPRequestHandler to start things off.  In them we
    will first identify the query string, and chop any query
    string off the path if necessary."""
    
    def do_GET(self):
        """Process GET browser requests.
        Processes URL parameters and then calls respond() with values found.
        """
        if self.path.find('?') != -1:
            self.path, self.query_string = self.path.split('?', 1)
        else:
            self.query_string = ''
        self.respond()

    def do_POST(self):
        """Process POST browser requests.
        Processes forms and then calls respond() with values found.
        
        Essentially a POST contains variables in just the same format as
        the GET query string, just passed in the body of the request.
        Potentially it could be in a different format (especially with
        file uploads), but we're not making a fully compliant server
        here.
        
        If client sends both URL (i.e. GET) ? params and POST form values the
        GET parameters are ignored.
        """
        if '?' in self.path:
            ## if we have GET params; be tolerant and ignore the GET
            self.path, throw_away_params = self.path.split('?', 1)
        self.query_string = self.rfile.read(int(self.headers['Content-Length']))
        self.respond()
        
    def respond(self):
        """Actually deal with GET/POST values and find/call function to
        handle request.
        """
        if running_under_cgi or (JYTHON_RUNTIME_DETECTED and sys.version_info < (2, 3)):
            # Jython 2.2
            # cgi parse doesn't seem to work under Jython 2.2
            if not self.path[1:]: # first character is a "/"
                function_name = 'index'
            else:
                function_name = self.path.lstrip('/')
        else:
            tmp_parse_result = cgi.parse_qsl(self.path, keep_blank_values=True)
            self.path = tmp_parse_result[0][0]
            function_name = self.path.lstrip('/')
        
        # We use the cgi module to parse the query string:
        get_params = dict(cgi.parse_qsl(self.query_string))
        if '' in get_params: # /index?=123, i.e. no variable name
            self.send_error(400, 'empty/missing variable/parameter name')
            return
        
        if PERFORM_UTF8_DECODING_ON_URL_PARAMETERS:
            ## FIXME TODO! Horrible encoding kludge - we assume utf8 if get failures drop down to supported encodings
            ## this should be the case BUT FireFox on WIndows XP (cp1252) will send latin1 if the originating from html did NOT specify an encoding
            try: #print self.headers
                encodings_list = self.headers['Accept-Charset']
                encodings_list = encodings_list.split(';')[0]
                encodings_list = encodings_list.split(',')
            except KeyError:
                encodings_list = [] # microB browser, TODO determine default charset, probably utf8
            for x in get_params:
                value_to_process = get_params[x]
                try:
                    dummy_value_to_process = value_to_process.decode('ascii')
                    ## If above succeeds, leave as (7bit) byte string
                except UnicodeDecodeError, info:
                    try:
                        # NOTE for utf-8 to be really used/sent by web broswers, <form accept-charset="utf-8"..> should be set
                        get_params[x] = value_to_process.decode('utf8')
                    except UnicodeDecodeError, info:
                        # go through list
                        for temp_encoding in encodings_list:
                            try:
                                get_params[x] = value_to_process.decode(temp_encoding)
                                break
                            except UnicodeDecodeError, info:
                                pass

        
        ## reset headers
        while response.headers:
            response.headers.popitem()
        response.headers['Content-Type'] = "text/html" # default
        response.headers['Accept-Charset'] = "utf-8" # default - NOTE for utf-8 to be really used/sent by web broswers, <form accept-charset="utf-8"..> should be set
        
        request.headers = self.headers
        request.remote.ip = self.client_address[0]
        request.path_info = self.path
        
        args = []
        page_gen = function_lookup(function_name)
        if page_gen is None:
            sys.stderr.write('function_name %r' % function_name) ## DEBUG
            sys.stderr.write(' get_params %r' % get_params) ## DEBUG
            sys.stderr.write('\n') ## DEBUG
            # This below is probably a noop as "is tuple" check below catches defaults
            page_gen = function_lookup('default') # NOTE this disables builtin favicon.ico support
            if page_gen:
                args = function_name.split('/')
        elif isinstance(page_gen, tuple):
            ## nasty check (and code)
            pobj, page_gen = page_gen
            args = function_name.split('/')
            if pobj is not dietcherry_root:
                args.pop(0) # ignore first piece, TODO for deeply nested dirs wont work (probably only works for nested 1 deep)
        if page_gen is not None:
            if not args:
                if inspect.isfunction(page_gen):
                    function_signature = inspect.getargspec(page_gen)
                    if not args and function_signature[1]:
                        # then we have function that has *args and is expecting path information
                        args = self.path.lstrip('/').split('/')
                        args.pop(0) # ignore first piece -- if not default.....
            try:
                try:
                    page_contents = page_gen(*args, **get_params)
                    ## See http://tools.cherrypy.org/wiki/MimeDecorator for cool type changer
                    ## also see http://www.thesamet.com/blog/2006/07/14/making-ie-cache-less/
                except HTTPRedirect, info:
                    redirect_url = info.urls[0]
                    response.status = info.httpcode
                    response.headers['Location'] = redirect_url
                    page_contents = 'Redirecting to <a href="%s">%s</a>'  % (redirect_url, cgi.escape(redirect_url))
                except HTTPError, info:
                    error_code, error_text, error_headers = info.error_tuple()
                    self.send_error(error_code, error_text)
                    return
            except Exception, info:
                # not 2.2
                stack_trace = traceback.format_exc()
                print stack_trace
                # FIXME this IF block needs refactoring and simplifying...
                server_error_code = 500
                if SERVER_ERRORS:
                    # NOTE IE 6 (and maybe others) hide custom error text. IE only shows HTTP error number.
                    ## FIXME add traceback to html?
                    if SERVER_ERRORS_TYPE=='basic':
                        #error_text = u"%s %s" % (Exception, info)
                        error_text = u"%r" % ((Exception, info), )
                        error_text += '\n' + stack_trace
                        error_text += '\nTo disable this debug output, set SERVER_ERRORS=False'
                        if sys.version_info >= (2, 3):
                            error_text = error_text.encode('us-ascii', 'xmlcharrefreplace')
                        else:
                            #error_text = error_text.encode('utf8')
                            error_text = xmlcharref_encode('us-ascii', error_text)
                        sys.stderr.write(error_text) ## FIXME make a logging statement?
                    else:
                        # "Advanced" html
                        bufferedfileptr = StringIO.StringIO()
                        exception_format_style = 'html'
                        #exception_format_style = 'text'
                        excep_formatter = cgitb.Hook(file=bufferedfileptr, format=exception_format_style)
                        excep_formatter.handle()
                        error_text_html = bufferedfileptr.getvalue()
                        if sys.version_info >= (2, 3):
                            error_text_html = error_text_html.encode('us-ascii', 'xmlcharrefreplace')
                        else:
                            #error_text_html = error_text_html.encode('utf8')
                            error_text_html = xmlcharref_encode('us-ascii', error_text_html)
                else:
                    error_text = 'Error serving page. Retry and if re-occurs contact the administrator'
                if SERVER_ERRORS and SERVER_ERRORS_TYPE=='html':
                    self.send_response(server_error_code)
                    self.send_header('Content-Type', 'text/html')
                    self.end_headers() ### FIXME missing many headers! Length in particular?
                    self.wfile.write(error_text_html)
                else:
                    self.send_error(500, error_text)
                if SERVER_ERRORS_FATAL:
                    raise
                return
        ## this is really the wrong place for this.. TODO re-factor, simple example of how to deal with it.
        elif function_name == 'favicon.ico' and FAVICON:
            page_contents = FAVICON
            response.headers['Content-Type'] = "image/x-icon"
        else:
            page_contents = '%s NOT found' % function_name
            self.send_error(404, page_contents)
            return
        #DEBUGPRINT print 'DEBUG', 'do_GET', page_contents
        request.headers = {} # sanity protection
        if hasattr(response, 'status'): 
            # TODO time this, may be quicker to try and except as this ends to be quicker in cpython
            # emulate cherry py custom error
            self.send_response(response.status)
            if hasattr(response, 'body'):
                page_contents = response.body
                del response.body
            # skip response.body, assume it was returned
            del response.status
        else:
            self.send_response(200)
        for header_name in response.headers:
            self.send_header(header_name, response.headers[header_name])
        self.end_headers() ### FIXME missing many headers! Length in particular?
        if isinstance(page_contents, unicode):
            if response.headers['Content-Type'] == "text/html": ## xhtml too?
                if sys.version_info >= (2, 3):
                    page_contents = page_contents.encode('ascii', 'xmlcharrefreplace')
                else:
                    #page_contents = page_contents.encode('utf8')
                    page_contents = xmlcharref_encode('us-ascii', page_contents)
            else:
                page_contents = page_contents.encode('utf8')
        self.wfile.write(page_contents)

def dietcherry_stop():
    global keep_serving
    keep_serving = False

def dietcherry_start(server_host=None, server_port=None, root_class=None, print_server_url=True):
    ### needs CTRL-C to stop! (Under Windows with CPython CTRL-Break instead)
    global dietcherry_root
    global dietcherry_fullmainurl
    
    if FAVICON_STR and not FAVICON:
        setup_favicon()
    
    if root_class is None:
        root_class = root
    dietcherry_root = root_class
    
    if DIETCHERRY_ENVIRONMENT!='cgi':
        SERVER_ERRORS_FATAL = False ## can't see use for this with internal server
        if server_host is None:
            server_host = ''  # NOTE IronPython doesn't like this
            server_host = config.get('server.socketHost') or config.get('server.socket_host') or server_host
        if server_port is None:
            #                       CherryPy 2.1 style                  CherryPy 3.x style
            server_port = config.get('server.socketPort') or config.get('server.socket_port')
            if server_port is None:
                server_port = 8000
        server_address = (server_host, server_port)
        
        if server_host == '':
            temp_server_address = ('localhost', server_address[1])
        else:
            temp_server_address = server_address
        dietcherry_fullmainurl='http://%s:%d' % temp_server_address 
        if print_server_url:
            print 'Serving on %s' % dietcherry_fullmainurl
            if sys.platform.startswith('win'):
                print '### CTRL-Break to stop!'
            else:
                print '### CTRL-C to stop!'
    else:
        server_address=None
    
    ## FIXME! if this is ran on the same port as an existing HTTPServer() get no errors :-(
    httpd = MyHTTPServer(server_address, HTTPHandler)
    #httpd.serve_forever()
    global server_is_ready
    server_is_ready = True
    if engine.callback_func:
        def _callback():
            server.wait() # probably redundant as we are here in server start and we just set server_is_ready true...
            engine.callback_func()
        threading.Thread(target=_callback).start()
    while keep_serving:
        httpd.handle_request()

# cherrypy.url clone/copy
def url(path="", qs="", script_name=None, base=None, relative=False):
    """most things are ignored....
    Returns the wrong thing if path is ommitted, does not print out current url
    This will probably fail under CGI mode, i.e. DIETCHERRY_ENVIRONMENT=='cgi'
    TODO copy cherrypy.url() code and tweak?
    """
    if path.startswith('/'):
        tmp_path=''
    else:
        tmp_path='/'
    tmp_path+=path
    return dietcherry_fullmainurl + tmp_path

class ServeDirClass(object):
    """CherryPy Object that can serve a static directory, performs NO caching (NOTE does not set cache headers)"""
    def __init__(self, fsdirpath, browsable=False):
        """params:
            fsdirpath is the local file system directory path
            browsable - can the directory be browsed or are only complete FILE paths allowed?
        """
        self.dirpath = os.path.abspath(fsdirpath)
        self.browsable = browsable
        if self.browsable:
            raise NotImplemented('browsable support')
        else:
            self.browsable_msg = """<html><head><title>Not Browsable
        </title></head><body>Not Browsable</body></html>"""
    
    def default(self, *args, **kwargs):
        """Serves requested files"""
        if args:
            path = os.path.join(self.dirpath, *args)
            return serve_file(path)
        else:
            # Probably the root of the static directory/url
            raise HTTPError(401)
    default.exposed = True
    __call__ = default ## needed for calling the directory root
    exposed = True # expose root object as callable

def ServeDir(fsdirpath, browsable=False):
    """params:
        fsdirpath is the local file system directory path
        browsable - can the directory be browsed or are only complete FILE paths allowed?
    """
    def serve_file_func(self, *args, **kwargs):
        """Serves requested files"""
        if args:
            path = os.path.join(fsdirpath, *args)
            return serve_file(path)
        else:
            # Probably the root of the static directory/url
            raise HTTPError(401)
    serve_file_func.exposed = True
    
    if browsable:
        raise NotImplemented('browsable support')
    return serve_file_func

##############################################################

if __name__ == '__main__':
    ## Stolen from http://blog.ianbicking.org/2008/10/24/decorators-and-descriptors
    # if this is used in a real app, consider renaming!
    # Decorator for (get only) descriptor
    class deco(object):
        def __init__(self, func):
            self.func = func
        def __call__(self, *args, **kwargs):
            resp = self.func(*args, **kwargs)
            return resp
        def __get__(self, obj, type=None):
            if obj is None:
                return self
            new_func = self.func.__get__(obj, type)
            return self.__class__(new_func)
            
    class SubWeb:
        def intadd(self, a, b):
            return int(a) + int(b) 
        intadd.exposed = True
        
        def default(self, *args, **kwargs):
            """Handle unhandled paths
            NOTE this disables builtin favicon.ico support"""
            return "vpath: %s</br>kwargs: %s</br> request.path_info: %s" % (args, kwargs, request.path_info)
        default.exposed = True
        
    class NonWeb:
        def intadd(self, a, b):
            return int(a) + int(b) 
        
    class MyTestBaseException(Exception):
        pass

    class SimpleWeb:
        
        # regular sub app (ala CherryPy), subapp needs to be exposed IN the class
        subapp=SubWeb()
        # example url; http://localhost:8000/subapp/intadd?a=1&b=34
        
        # Subapp that could be a regular object, that has no idea about 'expose'
        subappX=NonWeb()
        subappX.intadd = deco(subapp.intadd)
        subappX.intadd.exposed=True
        # example url; http://localhost:8000/subappX/intadd?a=1&b=34
        
        static = ServeDir('./')
        
        """demo basic features and different "expose" options"""
        def __init__(self):
            self.hello_world = """<html><head><title>single param test
            </title></head><body>%s</body></html>"""
        
        #@expose ## requires decorator syntax support (Python 2.4+), if used no need for exposed=True
        def index(self):
            #return self.hello_world % 'Hello World'
            #
            return '''
            <h1>Demo page</h1>
            path = ''' +  repr(request.path_info) + '''</br>
            
            <a href="/">this page / </a></br>
            <a href="/index">this page /index</a></br>
            <a href="/name">name demo with POST form</a></br>
            <a href="/name?instr=hello">name with GET param</a></br>
            <a href="/name_get">name_get</a></br>
            <a href="/name_get?instr=hello">name_get with GET param</a></br>
            <a href="/demo/how/default/works?p1=p1&p2=p2">demo default handler 1</a></br>
            <a href="/demo/how/default/works/hello%20world">demo default handler 2</a></br>
            <a href="/internal_error_test">internal_error_test demo</a></br>
            <a href="/internal_error_test_unicode">internal_error_test_unicode demo</a></br>
            <a href="/make404">make404</a></br>
            <a href="/redirect">redirect manual</a></br>
            <a href="/redirect_exception">redirect exception</a></br>
            <a href="/show_client_ip">show_client_ip</a></br>
            <a href="/show_client_info">show_client_info</a></br>
            <a href="/text">text demo</a></br>
            <a href="/auth_required">auth_required (not working demo)</a></br>
            <a href="/subapp/intadd?a=1&b=34"> Sub app demo 1a /subapp/intadd?a=1&b=34</a></br>
            <a href="/subapp/demo/how/default/works/hello%20world?p1=p1&p2=p2">Sub app demo 1b with default handler</a></br>
            <a href="/subappX/intadd?a=2&b=34">Sub app demo 2 with non CherryPy object /subappX/intadd?a=2&b=34</a></br>
            <a href="/static">static dir top (should display not auth'd message)</a></br>
            <a href="/static/dietcherrypy.py">static file dietcherrypy.py</a></br>
            <a href="/notablog">/notablog *arg support</a></br>
            <a href="/notablog/2010/07/31/hello%20world">/notablog/2010/07/31/hello%20world *arg support</a></br>
            
            </br></br>
            <a href="/stop">stop  the server</a></br>
            '''
        expose(index) # decorator function

        def name(self, instr=None):
            if instr is None:
                return """<form action="" method="POST" id="myform">
                        <input type="text" name="instr" value=""/>
                        <input type="submit" value="Submit"/></form>"""
            return self.hello_world % instr
        name.exposed = True ## Simple manual set .exposed attribute

        def name_get(self, instr=None):
            if instr is None:
                return """<form action="" method="GET" id="myform">
                        <input type="text" name="instr" value=""/>
                        <input type="submit" value="Submit"/></form>"""
            return self.hello_world % instr
        name_get.exposed = True ## Simple manual set .exposed attribute
        
        def default(self, *args, **kwargs):
            """Handle unhandled paths
            NOTE this disables builtin favicon.ico support"""
            return "main default handler</br>vpath: %s</br>kwargs: %s</br> request.path_info: %s" % (args, kwargs, request.path_info)
        default.exposed = True

        def internal_error_test(self, *args, **kwargs):
            """Show what happens when exceptions are raised"""
            x = 0 / 0
            return "will never see this"
        internal_error_test.exposed = True

        def internal_error_test_unicode(self, *args, **kwargs):
            """Show what happens when exceptions are raised"""
            raise MyTestBaseException(u'Unicode string; YEN SIGN \u00A5')
            return "will never see this"
        internal_error_test_unicode.exposed = True

        def make404(self, *args, **kwargs):
            """Fake a 404"""
            raise NotFound()
            return "will never see this"
        make404.exposed = True

        def redirect(self):
            """dumb redirect"""
            redirect_url = 'http://google.com'
            response.status = 302
            response.headers['Location'] = redirect_url
            return "should not see this text"
        redirect.exposed = True

        def redirect_exception(self):
            """dumb redirect via exception"""
            redirect_url = 'http://google.com'
            raise HTTPRedirect(redirect_url)
            return "should not see this text"
        redirect_exception.exposed = True

        def stop(self):
            server.stop()
            return """<html><head><title>stopping...</title></head>
            <body>stopping.....</body></html>"""
        stop.exposed = True

        # Commented out code shows alternative expose mechanisms
        # the ones used work on older versions of Python
        # (pre decorator support)
        #
        #@expose()
        def text(self):
            response.headers['Content-Type'] = 'text/plain'
            return self.hello_world % 'Hello World'
            #return """text example"""
        text.exposed = True
        #text = expose(text)
        
        def show_client_ip(self):
            response.headers['Content-Type'] = 'text/plain'
            return 'Your IP is %s' % request.remote.ip
        show_client_ip.exposed = True
        
        def show_client_info(self, *args, **kwargs):
            response.headers['Content-Type'] = 'text/plain'
            if json:
                client_headers = json.dumps(dict(request.headers), indent=4)
            else:
                client_headers = repr(dict(request.headers))  # stdlib including old versions of Python :-)
            return request.path_info + '\n' + repr(args) + '\n' + repr(kwargs) + '\n\n' + request.remote.ip + '\n\n' + client_headers 

        show_client_info.exposed = True
        
        def notablog(self, *args):
            """  
            http://127.0.0.1:8080/notablog/
            
            returns ""
            
            http://127.0.0.1:8080/notablog/2010/07/31/hello%20world
            
            returns, "2010-07-31-hello world"
            """
            print 'notablog called'
            # from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062
            this_function_name = sys._getframe().f_code.co_name
            this_function = getattr(self, this_function_name)
            return 'notablog</br>'+'-'.join(args) + '<br>'+repr(args)
        notablog.exposed=True
        
        def auth_required(self):
            response.status = 401
            ## NOTE WWW-Authenticate is one line, Apache CGI dislikes newlines in header value
            response.headers['WWW-Authenticate'] = '''Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"'''
            text_401 = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<HTML>
  <HEAD>
    <TITLE>Error</TITLE>
    <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
  </HEAD>
  <BODY><H1>401 Unauthorised.</H1></BODY>
</HTML>'''
            # either:
            response.body = text_401
            # or
            return text_401
            # doesn't matter which, diet cherry handles both
            # cherrypy.response.body takes precedence if there
        auth_required.exposed = True
        
    """
    ## CherryPy version 2.x.x style
    root = SimpleWeb()
    server.start()
    """
    
    ## CherryPy version 3.x style
    quickstart(SimpleWeb())
